﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using System.ComponentModel;
using Newtonsoft.Json;
using System.IO;
using Newtonsoft.Json.Linq;

namespace Dycox.ComponentModel
{
    [Serializable]
    public class StateObject : ISerializable, INotifyPropertyChanged
    {
        Dictionary<string, object> _dict;
        bool _init;

        public StateObject()
        {
            this._dict = new Dictionary<string, object>();
        }

        //public StateObject(IDictionary<string, object> dic)
        //{
        //    if (dic != null)
        //    {
        //        foreach (var pair in dic)
        //        {
        //            ValidateKey(pair.Key);
        //            ValidateValue(pair.Value);
        //        }
        //        this._dict = new Dictionary<string, object>(dic);
        //    }
        //    else
        //        this._dict = new Dictionary<string, object>();
        //}

        private StateObject(SerializationInfo info, StreamingContext context)
            : this()
        {
            object value;
            _init = true;
            foreach (var pair in info)
            {
                value = pair.Value;
                if (value is JValue)
                    value = ((JValue)value).Value;
                
                ValidateValue(value);
                _dict.Add(pair.Name, value);
            }
            _init = false;
        }

        private static void ValidateValue(object value)
        {
            if (value != null)
            {
                Type type = value.GetType();

                if (type.IsArray)
                {
                    //type = type.GetElementType();
                    //if (type == typeof(object) || type == typeof(StateObject))
                    {
                        foreach (object obj in (System.Collections.IEnumerable)value)
                            ValidateValue(obj);
                        return;
                    }
                }

                Type test = Nullable.GetUnderlyingType(type);
                if (test != null) type = test;

                TypeCode code = Type.GetTypeCode(type);

                switch (code)
                {
                    //case TypeCode.Object:
                    //    if (type != typeof(StateObject) && type != typeof(TimeSpan) &&
                    //        type != typeof(Guid))
                    //        goto case TypeCode.DBNull;
                    //    break;
                    case TypeCode.DBNull:
                    case TypeCode.Empty:
                        throw new NotSupportedException("Value type is not supported: " + type.FullName);
                }

            }
        }

        readonly static Regex KeyValidator = new Regex(@"[_a-zA-Z\$][a-zA-Z\d_\$]*", RegexOptions.Compiled);

        private static void ValidateKey(string key)
        {
            if (key == null) throw new ArgumentNullException("key");
            if (key.Length == 0) throw new ArgumentException("Key cannot be empty.");

            Match match = KeyValidator.Match(key);
            if (match.Success)
            {
                if (match.Value == key)
                    return;
            }

            throw new ArgumentException("Invalid key: " + key);
        }

        public bool HasValue(string key)
        {
            object value;

            if (_dict.TryGetValue(key, out value))
                return value != null;
            else
                return false;
        }

        public object GetValue(string key)
        {
            return this.GetValueOrDefault(key, null);
        }

        public object GetValueOrDefault(string key, object missing)
        {
            object value;
            if (_dict.TryGetValue(key, out value))
                return value;

            return missing;
        }

        public T GetValueOrDefault<T>(string key)
        {
            return this.GetValueOrDefault<T>(key, default(T));
        }

        public T GetValueOrDefault<T>(string key, T defaultValue)
        {
            if (this.HasValue(key))
                return this.GetValue<T>(key);
            else
                return defaultValue;
        }

        public object this[string key]
        {
            get { return this.GetValue(key); }
            set
            {
                this.SetValue(key, value);
            }
        }

        public void CopyTo(StateObject target)
        {
            if (target == null)
                throw new ArgumentNullException("target");

            foreach (var pair in _dict)
            {
                target[pair.Key] = pair.Value;
            }
        }

        public string[] Keys
        {
            get
            {
                return _dict.Keys.ToArray();
            }
        }

        //public void Merge(StateObject bag)
        //{
        //    Merge(bag, null);
        //}

        //public void Merge(StateObject bag, Func<string, object, bool> test)
        //{
        //    if (bag != null)
        //    {
        //        foreach (var pair in bag._dict)
        //        {
        //            if (test == null || test(pair.Key, pair.Value))
        //            {
        //                object value = pair.Value;
        //                if (value != null)
        //                {
        //                    Type type = value.GetType();

        //                    if (type.IsArray)
        //                    {
        //                        if (this.HasValue(pair.Key))
        //                            throw new NotSupportedException();
        //                        else
        //                            this.SetValue(pair.Key, value);
        //                    }
        //                    else if (type == typeof(StateObject))
        //                    {
        //                        object v0 = this.GetValue(pair.Key);
        //                        if (v0 == null) this.SetValue(pair.Key, value);
        //                        else ((StateObject)v0).Merge((StateObject)value, test);
        //                    }
        //                    else
        //                        this.SetValue(pair.Key, pair.Value);
        //                }
        //            }
        //        }
        //    }
        //}

        public int KeyCount
        {
            get { return _dict.Count; }
        }

        public void Remove(string key)
        {
            this.SetValue(key, null);
        }

        public void SetValue(string key, object value)
        {
            ValidateKey(key);
            ValidateValue(value);

            object oldValue;

            _dict.TryGetValue(key, out oldValue);

            if (value == null && _dict.ContainsKey(key)) _dict.Remove(key);
            else if (value != null)
            {
                if (_dict.ContainsKey(key)) _dict[key] = value;
                else _dict.Add(key, value);
            }

            if (!_init && !object.Equals(value, oldValue))
                OnPropertyChanged(key);
        }

        public T GetValue<T>(string key)
        {
            if (_dict.ContainsKey(key))
                return (T)GetValueOfType(key, typeof(T));
            else
                return default(T);
        }

        public object GetValueOfType(string key, Type targetType)
        {
            object value = this.GetValue(key);

            if (value != null && value != Type.Missing)
            {
                Type type1, type2 = value.GetType();
                if (targetType.IsAssignableFrom(type2)) return value;

                type1 = Nullable.GetUnderlyingType(targetType) ?? targetType;

                if (type1.IsEnum)
                {
                    if (type2 != typeof(string) && type1 != type2)
                    {
                        Type utype = Enum.GetUnderlyingType(type1);
                        if (utype != type2)
                        {
                            value = Convert.ChangeType(value, utype);
                            type2 = utype;
                        }
                    }

                    if (Enum.IsDefined(type1, value))
                    {
                        string text = value.ToString();
                        value = Enum.Parse(type1, text, true);
                        return value;
                    }
                    else
                        throw new InvalidOperationException(SR.GetString("invalid_enum_member", value, type1));
                }

                try
                {
                    object tmp = Convert.ChangeType(value, type1);
                    return tmp;
                }
                catch
                {
                }

                var tc = TypeDescriptor.GetConverter(type1);
                if (tc.CanConvertFrom(type2))
                    return tc.ConvertFrom(value);

                throw new InvalidOperationException("Cannot convert value from " + type2 + " to " + targetType);
            }
            else
            {
                return null;
            }
        }

        //public StateObject Clone()
        //{
        //    StateObject obj = new StateObject();

        //    obj._init = true;
        //    foreach (var pair in _dict)
        //    {
        //        object value = pair.Value;

        //        if (value != null)
        //        {
        //            if (value is StateObject)
        //            {
        //                value = ((StateObject)value).Clone();
        //            }
        //            else if (value.GetType().IsArray)
        //            {
        //                value = CloneArray((Array)value);
        //            }

        //            obj.SetValue(pair.Key, value);
        //        }
        //    }
        //    obj._init = false;

        //    return obj;
        //}

        //private object CloneArray(Array array)
        //{
        //    int length = array.Length;
        //    Array a1 = Array.CreateInstance(array.GetType().GetElementType(), length);

        //    for (int i = 0; i < length; i++)
        //    {

        //    }
        //}

        public string ToJson()
        {
            return ToJson(this);
        }

        internal static string ToJson(object obj)
        {
            JsonSerializer js = new JsonSerializer();
            js.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;

            StringWriter sw = new StringWriter();

            using (sw)
            {
                js.Serialize(sw, obj);
                return sw.ToString();
            }

        }

        public static StateObject FromJson(string jsonText)
        {
            if (string.IsNullOrWhiteSpace(jsonText))
                return new StateObject();

            return FromJson<StateObject>(jsonText);
        }

        internal static T FromJson<T>(string jsonText)
        {
            if (jsonText == null)
                throw new ArgumentNullException("jsonText");

            JsonSerializer js = new JsonSerializer();
            
            using (JsonReader reader = new JsonTextReader(new StringReader(jsonText)))
            {
                reader.CloseInput = true;
                return js.Deserialize<T>(reader);
            }
        }


        #region ISerializable 成员

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            foreach (var pair in _dict)
            {
                info.AddValue(pair.Key, pair.Value);
            }
        }

        #endregion

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string name)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(name));
        }

    }
}
